
Imports System
Imports System.Data
Imports System.Configuration
Imports System.Web
Imports System.Web.Security
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.WebControls.WebParts
Imports System.Web.UI.HtmlControls
Imports System.Web.SessionState
Imports System.Collections
Imports System.Collections.Generic
Imports System.Collections.Specialized
Imports System.Data.Common
Imports System.ComponentModel

Namespace Telerik.Web.SessionDS
	''' <summary>
	''' SessionDataSource is designed to be primarily used by the Telerik QSF examples.
	''' It acts as a wrapper around SqlDataSource that does not allow changes to be persisted
	''' to the underlying database, but instead stores them in the session state.
	''' This way every visitor of our site can study the controls, but any changes to the
	''' sample data will only be persisted for the duration of the session.
	''' It is not a general purpose solution and there are some limitations you need to know
	''' about if you are willing to use it in your custom scenario:
	''' 
	'''     1.  It is assumed that the tables in the underlying data source use 32-bit positive
	'''         integers for their primary keys. If they use other types or compound primary
	'''         keys consisting of other types, then you will not be able to insert rows.
	''' 
	'''     2.  Internally SessionDataSource assigns negative integers (from Int32.MinValue upwards)
	'''         for the newly added rows to avoid collisions with rows that have not been loaded yet.
	'''         Again, as with the first limitation, if you use negative integers for your primary
	'''         keys the behavior is unpredictable.
	''' 
	'''     3.	No constraints defined in the underlying data source are enforced. You are on your own.
	''' 
	''' Please note that the AutoIncrementFields from the previous versions has been renamed to PrimaryKeyFields.
	''' </summary>
    Public Class SessionDataSource
        Inherits SqlDataSource


        Public Sub New()




        End Sub



        Public Sub ClearSessionData()


            Me.Context.Session(DataSourceSessionKey) = Nothing

        End Sub



        Protected Overrides Function CreateDataSourceView(ByVal viewName As String) As SqlDataSourceView


            If MyBase.DesignMode Then



                Return MyBase.CreateDataSourceView(viewName)
            End If



            Return New SessionDataSourceView(Me, viewName, Me.Context, MyBase.CreateDataSourceView(viewName), Me.Context.Session)

        End Function



        Protected Overrides Sub OnLoad(ByVal e As EventArgs)


            MyBase.OnLoad(e)



            ' Reset cached data when page loads the first time.

            If Not MyBase.DesignMode AndAlso Not Me.Page.IsPostBack AndAlso ClearSessionOnInitialLoad Then



                Me.Context.Session(DataSourceSessionKey) = Nothing
            End If

        End Sub



        Public ReadOnly Property DataSourceSessionKey() As String


            Get



                Return SessionKey
            End Get
        End Property




        ''' <summary>

        ''' Comma delimited list of primary key fields (32-bit integers only).

        ''' </summary>




        <Description("Comma delimited list of primary key fields (32-bit integers only).")> _
        <NotifyParentProperty(True)> _
        <DefaultValue("")> _
        Public Property PrimaryKeyFields() As String


            Get


                Dim res As Object = Me.ViewState("pkFields")

                If res Is Nothing Then



                    res = String.Empty
                End If




                Return DirectCast(res, String)
            End Get

            Set(ByVal value As String)



                Me.ViewState("pkFields") = value
            End Set
        End Property




        ''' <summary>

        ''' Displays a warning that that the modifications will not be persisted.

        ''' </summary>



        <Description("Displays a warning that that the modifications will not be persisted.")> _
        <DefaultValue(True)> _
        Public Property DisplayWarning() As Boolean


            Get


                Dim warn As Object = Me.ViewState("displayWarning")

                If warn Is Nothing Then



                    warn = True
                End If




                Return CBool(warn)
            End Get

            Set(ByVal value As Boolean)



                Me.ViewState("displayWarning") = value
            End Set
        End Property




        ''' <summary>

        ''' Propagate changes to the original access data file.

        ''' </summary>


        <DefaultValue(False)> _
        Public Property RevertToOriginalDataSource() As Boolean


            Get
                If (Me.ViewState("_rtods") IsNot Nothing) Then
                    Return CBool(Me.ViewState("_rtods"))
                Else
                    Return False
                End If

            End Get

            Set(ByVal value As Boolean)
                Me.ViewState("_rtods") = value
            End Set
        End Property




        ''' <summary>

        ''' Reset the session field on initial load

        ''' </summary>




        <Description("Reset the session field on initial load.")> _
        <NotifyParentProperty(True)> _
        <DefaultValue("")> _
        Public Property ClearSessionOnInitialLoad() As Boolean


            Get

                If (ViewState("ClearSessionOnInitialLoad")) Then
                    Return CBool(Me.ViewState("ClearSessionOnInitialLoad"))
                Else
                    Return False
                End If

                'Return CBool(If(ViewState("ClearSessionOnInitialLoad"), True))
            End Get



            Set(ByVal value As Boolean)



                Me.ViewState("ClearSessionOnInitialLoad") = value
            End Set
        End Property





        <DefaultValue(True)> _
        Public Overrides Property Visible() As Boolean


            Get



                Return True
            End Get

            Set(ByVal value As Boolean)



                MyBase.Visible = value
            End Set
        End Property




        Public Property SessionKey() As String


            Get
                'If (DirectCast(ViewState("SessionKey"), String)) Then
                '    Return Page.ToString() + "_" + ID
                'Else
                '    Return ID
                'End If


                Return If(DirectCast(ViewState("SessionKey"), String), Page.ToString() + "_" + ID)
            End Get

            Set(ByVal value As String)



                ViewState("SessionKey") = value
            End Set
        End Property




        Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)


            MyBase.Render(writer)

            If Not Me.RevertToOriginalDataSource AndAlso DisplayWarning Then

                'writer.AddAttribute("style", "color:maroon;");

                'writer.RenderBeginTag(HtmlTextWriterTag.Div);

                'writer.Write("Note: The changes in the data will be persisted per Session only. The data will be reset next time you visit the page.");

                'writer.RenderEndTag();


            End If

        End Sub

    End Class



    Public Class SessionDataSourceView
        Inherits SqlDataSourceView


        Private originalView As DataSourceView

        Private session As HttpSessionState

        Private context As HttpContext

        Private owner As SessionDataSource

        Private SelectCom As DbCommand

        Private selectException As Exception

        Private currentSelectParameters As DbParameterCollection





        Private ReadOnly Property PrimaryKeyFields() As String()


            Get


                If owner.PrimaryKeyFields + "" <> "" Then



                    Return owner.PrimaryKeyFields.Split(","c)
                End If




                Return New String(-1) {}
            End Get
        End Property






        Public Sub New(ByVal owner As SqlDataSource, ByVal name As String, ByVal context As HttpContext, ByVal originalView As DataSourceView, ByVal session As HttpSessionState)

            MyBase.New(owner, name, context)


            Me.owner = TryCast(owner, SessionDataSource)

            Me.originalView = originalView

            Me.session = session


            Me.context = context
        End Sub



        Protected Overrides Function ExecuteSelect(ByVal arguments As DataSourceSelectArguments) As IEnumerable

            'Dim queryTable As DataTable

            'If Not (Me.owner.RevertToOriginalDataSource) Then

            'MyBase.ExecuteSelect(arguments)

            Dim queryTable As DataTable = DirectCast(MyBase.ExecuteSelect(arguments), DataView).Table

            'End If

            Dim sessionTable As DataTable = DirectCast(session(owner.DataSourceSessionKey), DataTable)



            InitPrimaryKey(queryTable)



            If owner.RevertToOriginalDataSource Then


                RaiseOnSelected(queryTable.DefaultView.Count)


                Return queryTable.DefaultView
            End If



            Dim result As DataTable

            If sessionTable Is Nothing Then


                sessionTable = queryTable.Copy()

                InitPrimaryKey(sessionTable)

                session(owner.DataSourceSessionKey) = sessionTable


                result = sessionTable
            Else




                result = SmartMerge(sessionTable, queryTable)
            End If



            Dim view As DataView = result.DefaultView

            view.Sort = arguments.SortExpression

            RaiseOnSelected(view.Count)

            Return view

        End Function



        Private Sub RaiseOnSelected(ByVal rowCount As Integer)

            Dim newArgs As New SqlDataSourceStatusEventArgs(SelectCom, rowCount, selectException)

            MyBase.OnSelected(newArgs)

        End Sub



        Protected Overrides Sub OnSelected(ByVal e As SqlDataSourceStatusEventArgs)

            SelectCom = e.Command

            selectException = e.Exception

        End Sub



        Protected Overrides Sub OnSelecting(ByVal e As SqlDataSourceSelectingEventArgs)


            currentSelectParameters = e.Command.Parameters

            MyBase.OnSelecting(e)

        End Sub



        Protected Overrides Function ExecuteDelete(ByVal keys As IDictionary, ByVal oldValues As IDictionary) As Integer


            If owner.RevertToOriginalDataSource Then



                Return MyBase.ExecuteDelete(keys, oldValues)
            End If



            Dim sessionTable As DataTable = DirectCast(session(owner.DataSourceSessionKey), DataTable)

            If sessionTable IsNot Nothing Then


                Dim [select] As String = Me.SelectRecordString(keys)



                Dim res As DataRow() = sessionTable.[Select]([select])

                If res.Length <> 1 Then



                    Throw New InvalidOperationException("Unable to locate record to delete. Please asure you have selected the DataKeyNames propery.")
                End If



                res(0).Delete()



                OnDataSourceViewChanged(EventArgs.Empty)




                Return 1
            End If



            Return 0

        End Function



        Protected Overrides Function ExecuteUpdate(ByVal keys As IDictionary, ByVal values As IDictionary, ByVal oldValues As IDictionary) As Integer


            If Me.owner.RevertToOriginalDataSource Then



                Return MyBase.ExecuteUpdate(keys, values, oldValues)
            End If



            Dim sessionTable As DataTable = DirectCast(Me.session(Me.owner.DataSourceSessionKey), DataTable)

            If sessionTable IsNot Nothing Then


                Dim [select] As String = Me.SelectRecordString(keys)



                Dim res As DataRow() = sessionTable.[Select]([select])

                If res.Length <> 1 Then



                    Throw New InvalidOperationException("Unable to locate record to update.")
                End If



                Dim rowToUpdate As DataRow = res(0)



                Try


                    For Each entry As DictionaryEntry In values

                        If (entry.Value IsNot Nothing AndAlso entry.Value.ToString() <> "") Then
                            rowToUpdate(DirectCast(entry.Key, String)) = entry.Value
                        Else
                            rowToUpdate(DirectCast(entry.Key, String)) = DBNull.Value
                        End If

                        'rowToUpdate(DirectCast(entry.Key, String)) = If(entry.Value IsNot Nothing AndAlso entry.Value.ToString() <> "", entry.Value, DBNull.Value)

                    Next

                Catch invalidValueException As ArgumentException


                    rowToUpdate.RejectChanges()


                    Throw invalidValueException
                End Try



                OnDataSourceViewChanged(EventArgs.Empty)




                Return 1
            End If



            Return 0

        End Function



        Protected Overrides Function ExecuteInsert(ByVal values As IDictionary) As Integer


            If Me.owner.RevertToOriginalDataSource Then
                Return MyBase.ExecuteInsert(values)
            End If



            Dim sessionTable As DataTable = DirectCast(session(owner.DataSourceSessionKey), DataTable)

            If sessionTable IsNot Nothing Then


                Dim newRow As DataRow = sessionTable.NewRow()

                For Each newValue As DictionaryEntry In values
                    'If (newValue.Value IsNot Nothing AndAlso newValue.Value.ToString() <> "") Then
                    '    newRow(DirectCast(newValue.Key, String)) = newValue.Value
                    'Else
                    '    newRow(DirectCast(newValue.Key, String)) = DBNull.Value
                    'End If

                    newRow(DirectCast(newValue.Key, String)) = If(newValue.Value IsNot Nothing AndAlso newValue.Value.ToString() <> "", newValue.Value, DBNull.Value)
                Next



                For Each entry As KeyValuePair(Of String, Integer) In GetNextPrimaryKey()



                    newRow(entry.Key) = entry.Value
                Next



                ExtractCommandParametersToDataRow(InsertParameters, newRow)



                sessionTable.Rows.Add(newRow)



                OnDataSourceViewChanged(EventArgs.Empty)




                Return 1
            End If



            Return 0

        End Function



        Private Sub ExtractCommandParametersToDataRow(ByVal parameters As ParameterCollection, ByVal row As DataRow)


            Dim paramsValues As IOrderedDictionary = parameters.GetValues(context, owner)

            For i As Integer = 0 To parameters.Count - 1


                If paramsValues(i) IsNot Nothing Then



                    row(parameters(i).Name) = paramsValues(i)

                End If
            Next

        End Sub



        Private Function FormatValue(ByVal value As Object) As String


            If TypeOf value Is String Then


                Dim str As String = DirectCast(value, String)


                Return ("'" + str.Replace("'", "''") + "'")
            End If



            If TypeOf value Is DateTime Then


                Dim [date] As DateTime = DirectCast(value, DateTime)


                Return ("'" + [date].ToString(System.Globalization.CultureInfo.InvariantCulture) + "'")
            End If



            Return value.ToString()

        End Function



        Private Function SelectRecordString(ByVal keys As IEnumerable) As String


            Dim [select] As String = String.Empty

            For Each entry As DictionaryEntry In keys


                If [select] <> String.Empty Then



                    [select] += " AND "
                End If



                If entry.Value IsNot Nothing Then



                    [select] += entry.Key + " = " + FormatValue(entry.Value)
                Else




                    [select] += entry.Key + " Is Null"

                End If
            Next



            Return [select]

        End Function



        Private Function SmartMerge(ByVal sessionTable As DataTable, ByVal queryTable As DataTable) As DataTable


            AddNewRows(sessionTable, queryTable)



            Dim mergeResult As DataTable = queryTable.Copy()

            mergeResult.Locale = System.Globalization.CultureInfo.InvariantCulture

            mergeResult.BeginLoadData()



            Dim added As DataTable = sessionTable.GetChanges(DataRowState.Added)

            If added IsNot Nothing Then


                For Each row As DataRow In added.Rows



                    mergeResult.Rows.Add(row.ItemArray)

                Next
            End If



            Dim deleted As DataTable = sessionTable.GetChanges(DataRowState.Deleted)

            If deleted IsNot Nothing Then


                For Each row As DataRow In deleted.Rows


                    Dim rowToDelete As DataRow = mergeResult.Rows.Find(GetPrimaryKeyValues(row, DataRowVersion.Original))

                    If rowToDelete IsNot Nothing Then



                        rowToDelete.Delete()

                    End If

                Next
            End If



            Dim modified As DataTable = sessionTable.GetChanges(DataRowState.Modified)

            If modified IsNot Nothing Then


                For Each row As DataRow In modified.Rows



                    mergeResult.LoadDataRow(row.ItemArray, True)

                Next
            End If



            mergeResult.EndLoadData()

            mergeResult.AcceptChanges()



            If owner.SelectCommand.Contains("WHERE") Then


                Dim result As DataTable = mergeResult.Copy()

                result.Rows.Clear()

                result.BeginLoadData()

                For Each row As DataRow In mergeResult.[Select](GetCurrentSelectFilterExpression())



                    result.Rows.Add(row.ItemArray)
                Next

                result.EndLoadData()

                result.AcceptChanges()




                Return result
            End If



            Return mergeResult

        End Function



        Private Shared Sub AddNewRows(ByVal targetTable As DataTable, ByVal newTable As DataTable)


            If targetTable.PrimaryKey.Length = 0 OrElse newTable.PrimaryKey.Length = 0 Then



                Return
            End If



            For Each row As DataRow In newTable.Rows


                Dim rowKey As Object() = GetPrimaryKeyValues(row, DataRowVersion.Original)

                Dim existingRow As DataRow = targetTable.Rows.Find(rowKey)

                If existingRow Is Nothing Then


                    Dim deletedRow As DataRow = FindDeletedRow(targetTable, rowKey)

                    If deletedRow Is Nothing Then



                        targetTable.Rows.Add(row.ItemArray).AcceptChanges()

                    End If

                End If
            Next

        End Sub



        Private Function GetCurrentSelectFilterExpression() As String


            Const whereStr As String = "WHERE"

            Dim ixWhere As Integer = owner.SelectCommand.IndexOf(whereStr, StringComparison.InvariantCultureIgnoreCase)

            If ixWhere = -1 Then



                Return String.Empty
            End If



            Dim selectCriteria As String = owner.SelectCommand.Substring(ixWhere + whereStr.Length)



            For Each param As DbParameter In currentSelectParameters



                selectCriteria = selectCriteria.Replace(param.ParameterName, FormatValue(param.Value))
            Next



            Return selectCriteria

        End Function



        Private Sub InitPrimaryKey(ByVal table As DataTable)


            Dim pk As DataColumn() = New DataColumn(PrimaryKeyFields.Length - 1) {}

            For i As Integer = 0 To PrimaryKeyFields.Length - 1


                Dim column As DataColumn = table.Columns(PrimaryKeyFields(i))

                column.[ReadOnly] = True


                pk(i) = column
            Next



            table.PrimaryKey = pk

        End Sub



        Private Shared Function GetPrimaryKeyValues(ByVal row As DataRow, ByVal version As DataRowVersion) As Object()


            Dim pk As Object() = New Object(row.Table.PrimaryKey.Length - 1) {}



            For i As Integer = 0 To pk.Length - 1



                pk(i) = row(row.Table.PrimaryKey(i), version)
            Next



            Return pk

        End Function



        Private Shared Function CompareRowKeys(ByVal rowKey As Object(), ByVal candidateKey As Object()) As Boolean


            Dim candidateMatches As Boolean = True

            For i As Integer = 0 To candidateKey.Length - 1


                If Not rowKey(i).Equals(candidateKey(i)) Then


                    candidateMatches = False


                    Exit For

                End If
            Next

            Return candidateMatches

        End Function



        Private Shared Function FindDeletedRow(ByVal table As DataTable, ByVal rowKey As Object()) As DataRow


            Dim deletedRecords As DataTable = table.GetChanges(DataRowState.Deleted)

            Dim deletedRow As DataRow = Nothing

            If deletedRecords IsNot Nothing Then


                For Each candidateRow As DataRow In deletedRecords.Rows


                    Dim candidateKey As Object() = GetPrimaryKeyValues(candidateRow, DataRowVersion.Original)

                    Dim candidateMatches As Boolean = CompareRowKeys(rowKey, candidateKey)



                    If candidateMatches Then



                        deletedRow = candidateRow

                    End If

                Next
            End If

            Return deletedRow

        End Function



        Private Function GetNextPrimaryKey() As Dictionary(Of String, Integer)


            Dim pkSeedsSessionKey As String = owner.DataSourceSessionKey + "_pkSeeds"

            Dim pkSeeds As Dictionary(Of String, Integer) = DirectCast(session(pkSeedsSessionKey), Dictionary(Of String, Integer))



            If pkSeeds Is Nothing Then


                pkSeeds = New Dictionary(Of String, Integer)()

                For Each pkField As String In PrimaryKeyFields



                    pkSeeds(pkField) = Integer.MinValue
                Next


                session(pkSeedsSessionKey) = pkSeeds
            End If



            Dim newKeys As New Dictionary(Of String, Integer)()

            For Each pkField As String In PrimaryKeyFields


                newKeys(pkField) = pkSeeds(pkField)


                pkSeeds(pkField) = pkSeeds(pkField) + 1
            Next



            Return newKeys

        End Function

    End Class
End Namespace

'=======================================================
'Service provided by Telerik (www.telerik.com)
'Conversion powered by NRefactory.
'Twitter: @telerik, @toddanglin
'Facebook: facebook.com/telerik
'=======================================================
